using System;
using System.Collections.Generic;
using System.Text;
using System.Threading;

namespace Decav.Collections
{

    /// <summary>
    /// A queue that blocks on output if nothing is waiting in the queue and supports
    /// closing the queue via a call to Close or an external wait handle.
    /// </summary>
    /// <typeparam name="T">The data type that the queue contains.</typeparam>
    public class AbortableBlockingQueue<T> : IDisposable
    {

        #region Fields

        /// <summary>
        /// Whether or not this instance is already closed.
        /// </summary>
        private bool _isDisposed;

        /// <summary>
        /// Whether or not to set <see cref="CloseWaitHandle"/> when <see cref="Close"/> is called.
        /// </summary>
        private bool _setWaitHandleOnClose;

        /// <summary>
        /// Whether or not to close <see cref="CloseWaitHandle"/> when <see cref="Close"/> is called.
        /// </summary>
        private bool _disposeWaitHandleOnClose;

        /// <summary>
        /// The wait handle to wait on if the queue is closed. May be specified externally.
        /// </summary>
        private EventWaitHandle _closeWaitHandle;

        /// <summary>
        /// The wait handle to wait on when items are not available to dequeue.
        /// </summary>
        private AutoResetEvent _itemsAvailableWaitHandle;

        /// <summary>
        /// An array of waithandles to be passed to WaitHandle.WaitAny.
        /// </summary>
        private WaitHandle[] _waitHandles;

        /// <summary>
        /// Holds the actual queue collection implementation.
        /// </summary>
        private Queue<T> _queue;

        #endregion

        #region Constructors, Destructors, Dispose

        /// <summary>
        /// Initializes a new instance of the <see cref="AbortableBlockingQueue&lt;T&gt;"/> class.
        /// </summary>
        public AbortableBlockingQueue( )
            : this( new ManualResetEvent( false ), true, 100 )
        {
            _disposeWaitHandleOnClose = true;
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="AbortableBlockingQueue&lt;T&gt;"/> class.
        /// </summary>
        /// <param name="capacity">The initial capacity of the queue capacity. Will resize if needed.</param>
        public AbortableBlockingQueue( int capacity )
            : this( new ManualResetEvent( false ), true, capacity )
        {
            _disposeWaitHandleOnClose = true;
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="AbortableBlockingQueue&lt;T&gt;"/> class.
        /// </summary>
        /// <param name="closeWaitHandle">An external wait handle that when set, will close the queue causing Dequeue operations to unblock and return false.</param>
        /// <param name="setOnClose">if set to <c>true</c> the wait handle specified by <see cref="closeWaitHandle"/> will be set when Close or Dispose is called on this class explicitly.</param>
        public AbortableBlockingQueue( EventWaitHandle closeWaitHandle, bool setOnClose )
            : this( closeWaitHandle, setOnClose, 100 )
        {
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="AbortableBlockingQueue&lt;T&gt;"/> class.
        /// </summary>
        /// <param name="closeWaitHandle">An external wait handle that when set, will close the queue causing Dequeue operations to unblock and return false.</param>
        public AbortableBlockingQueue( EventWaitHandle closeWaitHandle )
            : this( closeWaitHandle, false, 100 )
        {
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="AbortableBlockingQueue&lt;T&gt;"/> class.
        /// </summary>
        /// <param name="closeWaitHandle">An external wait handle that when set, will close the queue causing Dequeue operations to unblock and return false.</param>
        /// <param name="setOnClose">if set to <c>true</c> the wait handle specified by <see cref="closeWaitHandle"/> will be set when Close or Dispose is called on this class explicitly.</param>
        /// <param name="capacity">The initial capacity of the queue capacity. Will resize if needed.</param>
        public AbortableBlockingQueue( EventWaitHandle closeWaitHandle, bool setOnClose, int capacity )
        {

            if ( closeWaitHandle == null )
                throw new ArgumentNullException( "closeWaitHandle" );

            _closeWaitHandle = closeWaitHandle;
            _setWaitHandleOnClose = setOnClose;
            _queue = new Queue<T>( capacity );
            _itemsAvailableWaitHandle = new AutoResetEvent( false );

            _waitHandles = new WaitHandle[] { _closeWaitHandle, _itemsAvailableWaitHandle };

        }

        /// <summary>
        /// Releases unmanaged resources and performs other cleanup operations before the
        /// <see cref="AbortableBlockingQueue&lt;T&gt;"/> is reclaimed by garbage collection.
        /// </summary>
        ~AbortableBlockingQueue( )
        {
            Dispose( false );
        }

        /// <summary>
        /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
        /// </summary>
        /// <param name="disposing">True if a manual dispose, false if by GC.</param>
        private void Dispose( bool disposing )
        {
            
            if ( disposing && !IsDisposed )
            {

                // Closes the CloseWaitHandle if it's not an external handle
                // or the caller asked us to set/close it on dispose
                EventWaitHandle closeWaitHandle = _closeWaitHandle;
                if ( closeWaitHandle != null )
                {
                    if ( _setWaitHandleOnClose )
                        closeWaitHandle.Set( );
                    if ( _disposeWaitHandleOnClose )
                        closeWaitHandle.Close( );
                }   // if

                // Closes the ItemsAvailableWaitHandle which will cause Dequeue to unblock
                // If the above piece of code already set and closed CloseWaitHandle, this
                // may have already occurred.
                EventWaitHandle itemsAvailableWaitHandle = _itemsAvailableWaitHandle;
                if ( itemsAvailableWaitHandle != null )
                    itemsAvailableWaitHandle.Close( );

                _isDisposed = true;

            }   // if

        }

        /// <summary>
        /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
        /// </summary>
        public void Dispose( )
        {
            Dispose( false );
            GC.SuppressFinalize( this );
        }

        /// <summary>
        /// Checks if the current instance is already disposed and if so, throws an
        /// <see cref="ObjectDisposedException"/>.
        /// </summary>
        protected void CheckIfDisposed( )
        {
            if ( IsDisposed )
                throw new ObjectDisposedException( GetType( ).Name );
        }

        /// <summary>
        /// Gets a boolean indicating whether or not this instance has already been disposed.
        /// </summary>
        protected bool IsDisposed
        {
            get
            {
                return _isDisposed;
            }
            private set
            {
                if ( _isDisposed && !value )
                    throw new InvalidOperationException( "Unable to set IsDisposed to false after it's been set to true." );
                _isDisposed = true;
            }
        }

        #endregion

        #region Properties

        /// <summary>
        /// Gets a wait hamdle that is used to block until the queue is closed.
        /// </summary>
        /// <remarks>
        /// If this is not specified in the constructor, a default wait handle is used.
        /// If this is specified in the constructor, an external wait condition can be used
        /// to cause the queue to close automatically. Once this is set, the queue can no
        /// longer be used.
        /// </remarks>
        public WaitHandle CloseWaitHandle
        {
            get
            {
                return _closeWaitHandle;
            }
        }

        /// <summary>
        /// Gets a wait handle that is used to determine if items are currently available.
        /// </summary>
        /// <remarks>
        /// External code can wait on this handle, but should not attempt to set it directly
        /// which could cause unpredictable behavior.
        /// </remarks>
        public WaitHandle ItemsAvailableWaitHandle
        {
            get
            {
                return _itemsAvailableWaitHandle;
            }
        }

        /// <summary>
        /// Gets an array of waithandles in the order of Close, ItemsAvailable that can
        /// be passed to <see cref="WaitHandle.WaitAny"/>.
        /// </summary>
        protected WaitHandle[] WaitHandles
        {
            get
            {
                return _waitHandles;
            }
        }

        /// <summary>
        /// Gets the count of items currently in the queue.
        /// </summary>
        public int Count
        {
            get
            {
                lock ( _queue )
                {
                    return _queue.Count;
                }   // lock
            }
        }

        #endregion

        #region Methods

        /// <summary>
        /// Closes the queue which wakes any blocking calls to Dequeue and sets the
        /// closed waithandle.
        /// </summary>
        public void Close( )
        {
            Dispose( );
        }

        /// <summary>
        /// Enqueues the item into the queue without timing out. A blocking call to Dequeue will be
        /// resumed to receive the item.
        /// </summary>
        /// <param name="item">The item to add to the queue.</param>
        /// <returns>True if the item was queued, always returns true in this implementation.</returns>
        public bool Enqueue( T item )
        {
            lock ( _queue )
            {
                _queue.Enqueue( item );
                _itemsAvailableWaitHandle.Set( );
            }   // lock

            return true;

        }

        /// <summary>
        /// Dequeues an item from the queue without timing out. The call will block until an
        /// item is available in the queue.
        /// </summary>
        /// <param name="item">Receives the item that was dequeued.</param>
        /// <returns>True if an object was successfully dequeued or false if the queue timed out or the queue was closed.</returns>
        public bool Dequeue( out T item )
        {
            return Dequeue( out item, Timeout.Infinite );
        }

        /// <summary>
        /// Dequeues an item from the queue without timing out. The call will block until an
        /// item is available in the queue.
        /// </summary>
        /// <param name="timeout">The timeout, as a TimeSpan. Use Timeout.Infinite to never timeout.</param>
        /// <param name="item">Receives the item that was dequeued.</param>
        /// <returns>True if an object was successfully dequeued or false if the queue timed out or the queue was closed.</returns>
        public bool Dequeue( out T item, TimeSpan timeout )
        {
            return Dequeue( out item, (int)timeout.TotalMilliseconds );
        }

        /// <summary>
        /// Dequeues an item from the queue without timing out. The call will block until an
        /// item is available in the queue.
        /// </summary>
        /// <param name="item">Receives the item that was dequeued.</param>
        /// <param name="timeout">The timeout, in milliseconds. Use Timeout.Infinite to never timeout.</param>
        /// <returns>
        /// True if an object was successfully dequeued or false if the queue timed out or the queue was closed.
        /// </returns>
        public bool Dequeue( out T item, int timeout )
        {

            // Wait for items to become available
            if ( WaitHandle.WaitAny( WaitHandles, timeout, false ) == 1 )
            {

                lock ( _queue )
                {

                    // Process our item
                    item = _queue.Dequeue( );

                    // If there was more than one item, set this so we can allow
                    // another waiting thread to succeed.
                    if ( _queue.Count > 1 )
                        _itemsAvailableWaitHandle.Set( );

                }   // lock

                return true;

            }   // if
            else
            {
                
                // Didn't get one in time or the queue was closed
                item = default( T );
                return false;

            }   // else

        }

        #endregion

    }   // class

}   // namespace
